#!/usr/bin/env python
# -*- encoding: utf-8 -*-
'''
@NAME          : request_logger.py
@DESCRIPTION   : 请求日志中间件，统一处理和记录用户访问信息
'''
import os, json, re
from flask import request, g, current_app
from datetime import datetime, timezone
import threading
import user_agents  # 用于解析User-Agent
from typing import Optional, Dict, Any
from flask_login import current_user
from flask_jwt_extended import get_jwt_identity, verify_jwt_in_request

from app.common.security import sanitize_input

from app.middlewares.rate_limit_decorator import file_manager

def init_request_logger(app):
    """初始化请求日志中间件"""

    # 应用启动时直接清理，而不是等待第一个请求
    with app.app_context():
        # 清理过期的防刷用户的json记录
        file_manager.clean_old_files(days=2)
        app.logger.info("已清理过期的速率限制文件")

    @app.before_request
    def sanitize_request_data():
        """自动清洗请求数据，防止XSS攻击"""
        # 跳过静态资源请求
        if request.path.startswith('/static/') or request.path.startswith('/sse/'):
            return
            
        # 只对特定路径进行清洗
        sanitize_paths = ['/user/register', '/user/login', '/comment/add']

        if request.path not in sanitize_paths:
            return

        if request.method in ['POST', 'PUT', 'PATCH']:
            # 清洗表单数据
            if request.form:
                # 注意：不能直接修改request.form，因为它是不可变对象
                # 相反，我们将清洗后的数据存储在g对象中

                sanitized_data = sanitize_input(request.form.to_dict())
            
                # 密码字段特殊处理：保持原样
                if 'password' in request.form:
                    sanitized_data['password'] = request.form.get('password')

                # 密码字段特殊处理：保持原样
                if 'csrf_token' in request.form:
                    sanitized_data['csrf_token'] = request.form.get('csrf_token')

                g.sanitized_form = sanitized_data
            
            # 清洗JSON数据
            if request.is_json:
                sanitized_data = sanitize_input(request.get_json())
            
                # 密码字段特殊处理：保持原样
                if 'password' in request.get_json():
                    sanitized_data['password'] = request.get_json().get('password')
                
                g.sanitized_json = sanitized_data
                
    @app.after_request
    def log_request_info(response):
        """在每个请求处理后记录访问信息"""
        # 跳过静态资源请求
        if request.path.startswith('/static/') or request.path.startswith('/sse/'):
            return response
        
        # 收集客户端信息并记录
        _act_client_info(response)
        
        return response
    
def _get_route_dict() -> Dict[str, Any]:
    """获取路由映射字典"""
    _type_map = os.path.join(current_app.config.get('APPLICATION_ROOT'), "user_action_type.json")

    _action_types = {}
    if os.path.exists(_type_map):
        with open(_type_map, 'r', encoding='utf-8') as f:
            _action_types = json.load(f)

    return _action_types
    
def _act_client_info(response):
    """在每个请求处理后记录访问信息"""

    # 需要收集客户端信息的请求路径
    _req_paths = _get_route_dict().keys()

    # 如果路径中存在?,则路径中获取?之前的部分
    _no_path = request.path
    if '?' in _no_path:
        _no_path = _no_path.split('?')[0]

    if _no_path not in _req_paths : # 跳过不需要记录的请求
        return response
        
    # 获取用户ID：依次尝试JWT、Flask-Login和请求头
    user_id = _get_user_id()

    if not user_id: # 未登录用户不记录
        return response
    
    # 收集客户端信息
    client_info = _collect_client_info(user_id)
    
    # 记录请求路径和方法
    client_info['path'] = request.path
    client_info['method'] = request.method
    
    # 确定行为类型和目标
    _at, _tt = _determine_action_type(request.path, request.method)
    client_info['action_type'] = _at
    client_info['target_type'] = _tt
    client_info['target_id'] = _get_target_id(request.path)
    
    # 异步写入数据库
    _async_save_log(client_info)
    

def _get_user_id() -> Optional[int]:
    """获取当前用户ID，依次尝试各种认证方式"""
    # 方式1: 使用Flask-Login
    if hasattr(current_user, 'is_authenticated') and current_user.is_authenticated:
        return current_user.user_id
        
    # 方式2: 使用JWT
    try:
        verify_jwt_in_request(optional=True)
        jwt_identity = get_jwt_identity()
        if jwt_identity:
            return jwt_identity
    except Exception:
        pass
        
    # 方式3: 从请求头获取
    if 'X-User-ID' in request.headers:
        try:
            return int(request.headers.get('X-User-ID'))
        except (ValueError, TypeError):
            pass
            
    # 方式4: 从Flask g对象获取
    if hasattr(g, 'user') and g.user:
        return g.user.user_id
    
    # 未登录
    return None

def _collect_client_info(user_id: Optional[int] = None) -> Dict[str, Any]:
    """收集客户端信息"""
    # 解析User-Agent
    ua_string = request.user_agent.string
    user_agent = user_agents.parse(ua_string)
    
    # 获取IP地址（考虑代理情况）
    if request.headers.get('X-Forwarded-For'):
        ip = request.headers.get('X-Forwarded-For').split(',')[0]
    else:
        ip = request.remote_addr
    
    # 收集信息
    return {
        'user_id': user_id,
        'ip_address': ip,
        'browser': f"{user_agent.browser.family} {user_agent.browser.version_string}",
        'os': f"{user_agent.os.family} {user_agent.os.version_string}",
        'device': user_agent.device.family,
        'is_mobile': user_agent.is_mobile,
        'is_tablet': user_agent.is_tablet,
        'is_pc': user_agent.is_pc,
        'access_time': datetime.now(timezone.utc),
        'referrer': request.referrer,
        'user_agent': ua_string
    }

def _determine_action_type(path: str, method: str) -> int:
    """
    根据请求路径和方法确定行为类型, 返回行为类型和目标类型（1-用户；2-主贴；3-版块；4-回复；5-资源；）
    默认返回 0-页面访问；1-用户；
    
    """
    # 行为类型映射json文件
    '''
        参考格式：
        {
            "/user/login": {
                "code": 1,
                "target": 1,
                "method": "POST",
                "description": "用户登录"
            },
            "/user/logout": {
                "code": 2,
                "target": 1,
                "method": "POST",
                "description": "用户退出登录"
            }
            ....
        }
    '''
    _action_types = _get_route_dict()
    

    # 从配置文件中查找
    '''
        # 登录行为
        ('/api/auth/v1/login', 'POST', 1): 2,
    '''
    action_types = {}
    for k, v in _action_types.items():
        action_types[(k, v.get('method','POST,GET')), v.get('target', 1)] = int(v.get('code'))
    
    # 查找出匹配的行为类型和目标类型
    for (p, m, t), code in action_types.items():
        if path.startswith(p) and method in m:
            return code, t
    
    # 默认为页面访问
    return 0, 1

def _get_target_id(path: str) -> int:
    """
    根据请求路径确定目标ID
    """
    # 示例：从路径解析目标类型和ID
    # 如：/user/profile?id=123 -> 123
    
    # 使用正则表达式匹配
    # 例如：/user/profile/123或/user/profile?id=123
    _target_id = re.search(r'/user/profile/(\d+)', path)
    if _target_id:
        return int(_target_id.group(1))
    else:
        _target_id = re.search(r'id=(\d+)', path)
        if _target_id:
            return int(_target_id.group(1))
        else:
            return 0

def _async_save_log(client_info: Dict[str, Any]) -> None:
    """异步保存日志到数据库"""
    def save_to_db(app, info):
        with app.app_context():
            try:
                # 准备日志数据
                log_data = {
                    'user_id': info.get('user_id'),  # 未登录用户不记录日志
                    'action_type': info.get('action_type', 1),  # 默认为页面访问
                    'target_type': info.get('target_type', 0), 
                    'target_id': info.get('target_id', 0),
                    'client_info': {   # 客户端信息存入JSON字段
                        'ip_address': info.get('ip_address'),
                        'browser': info.get('browser'),
                        'os': info.get('os'),
                        'device': info.get('device'),
                        'is_mobile': info.get('is_mobile'),
                        'is_tablet': info.get('is_tablet'),
                        'is_pc': info.get('is_pc'),
                        'user_agent': info.get('user_agent'),
                        'referrer': info.get('referrer'),
                        'path': info.get('path'),
                        'method': info.get('method')
                    },
                    'create_time': info.get('access_time')
                }
                
                # 使用UserService创建行为日志
                from app.service.user_service import UserService
                result = UserService.user_log_action(
                    user_id=log_data['user_id'],
                    action_data=log_data
                )
                
                # 检查结果
                if not result.get('success'):
                    current_app.logger.error(f"保存访问日志失败: {result.get('message')}")
                    
            except Exception as e:
                current_app.logger.error(f"记录访问日志时发生未知错误: {str(e)}")
    
    # 启动线程异步保存
    thread = threading.Thread(
        target=save_to_db,
        args=(current_app._get_current_object(), client_info)
    )
    thread.daemon = True
    thread.start()